package org.javadatabasemigrations;

import java.util.ArrayList;
import java.util.List;

import org.javadatabasemigrations.exceptions.IllegalMigrationClassException;
import org.javadatabasemigrations.exceptions.MigrationException;
import org.javadatabasemigrations.util.PackageUtils;


public class MigrationUtils {

    private static final String MIGRATION_CLASS_NAME = "M\\d+_.*";


    public static MigrationRunner migrationRunnerFromPackage(
                                                             final String packageName) {

        List<Class<Migration>> migrationClasses = migrationClassesFromPackage(packageName);
        List<Migration> migrationInstances = migrationsFromClasses(migrationClasses);

        MigrationRunner runner = new MigrationRunner();
        runner.addMigrations(migrationInstances);
        return runner;
    }


    public static List<Migration> migrationsFromPackage(String packageName) {

        return migrationsFromClasses(migrationClassesFromPackage(packageName));
    }


    public static List<Migration> migrationsFromClasses(
                                                        final List<Class<Migration>> migrationClasses) {

        List<Migration> migrations = new ArrayList<Migration>();
        for (Class<Migration> migrationClass : migrationClasses) {
            Migration migration = migrationFromClass(migrationClass);
            migrations.add(migration);
        }
        return migrations;
    }


    public static List<Class<Migration>> migrationClassesFromPackage(
                                                                     final String packageName) {

        List<Class> classesInPackage = getClassesInPackage(packageName);
        List<Class<Migration>> migrationClasses = getMigrationClasses(classesInPackage);
        return migrationClasses;
    }


    public static List<Class<Migration>> getMigrationClasses(
                                                             final List<Class> classes) {

        List<Class<Migration>> migrationClasses = new ArrayList<Class<Migration>>();
        for (Class clazz : classes) {
            if (!isMigrationClass(clazz)) {
                continue;
            }
            migrationClasses.add(clazz);
        }
        return migrationClasses;
    }


    public static void verifyIsMigrationClass(final Class clazz) {

        if (!isMigrationClass(clazz)) {
            throw new IllegalMigrationClassException(
                "The class ["
                    + clazz.getName()
                    + "] does not qualify as a migration class. "
                    + "See the requirements for migration classes to fix this problem.");
        }
    }


    public static boolean isMigrationClass(final Class clazz) {

        return Migration.class.isAssignableFrom(clazz)
            && isMigrationClassName(clazz);
    }


    public static Version getMigrationVersion(
                                              final Class<? extends Migration> clazz) {

        MigrationUtils.verifyIsMigrationClass(clazz);

        String className = clazz.getSimpleName();
        String versionPart = className.split("_")[0];
        String versionString = versionPart.replace("M", "");
        Version version = new Version(Integer.parseInt(versionString));
        return version;
    }


    private static Migration migrationFromClass(
                                                final Class<Migration> migrationClass) {

        try {
            Migration migration = migrationClass.newInstance();
            return migration;
        } catch (InstantiationException e) {
            throw new MigrationException(
                "Unable to instantiate migration of class ["
                    + migrationClass.getCanonicalName() + "].", e);
        } catch (IllegalAccessException e) {
            throw new MigrationException(
                "Unable to instantiate migration of class ["
                    + migrationClass.getCanonicalName() + "].", e);
        }
    }


    private static boolean isMigrationClassName(final Class clazz) {

        String name = clazz.getSimpleName();
        return name.matches(MIGRATION_CLASS_NAME);
    }


    private static List<Class> getClassesInPackage(final String packageName) {

        List<Class> classesInPackage;
        try {
            classesInPackage = PackageUtils.classesInPackage(packageName);
        } catch (ClassNotFoundException e) {
            throw new MigrationException(
                "Unable to load classes from package [" + packageName + "].", e);
        }
        return classesInPackage;
    }

}
